// -*- c -*-
//
// %CopyrightBegin%
//
// Copyright Ericsson AB 2017-2023. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// %CopyrightEnd%
//

//
// Define a regular expression that will match instructions that
// perform GC.  That will allow beam_makeops to check for instructions
// that don't use $REFRESH_GEN_DEST() when they should.
//

GC_REGEXP=erts_garbage_collect|erts_gc|GcBifFunction;

// $Offset is relative to the start of the instruction (not to the
// location of the failure label reference). Since combined
// instructions may increment the instruction pointer (e.g. in
// 'increment') for some of the instructions in the group, we actually
// use a virtual start position common to all instructions in the
// group. To calculate the correct virtual position, we will need to
// add $IP_ADJUSTMENT to the offset. ($IP_ADJUSTMENT will usually be
// zero, except in a few bit syntax instructions.)

SET_I_REL(Offset) {
    ASSERT(VALID_INSTR(*(I + ($Offset) + $IP_ADJUSTMENT)));
    I += $Offset + $IP_ADJUSTMENT;
}

SET_CP_I_ABS(Target) {
    c_p->i = $Target;
    ASSERT(VALID_INSTR(*(const BeamInstr*)c_p->i));
}

SET_REL_I(Dst, Offset) {
    $Dst = I + ($Offset);
    ASSERT(VALID_INSTR(*(const BeamInstr*)$Dst));
}

FAIL(Fail) {
    //| -no_prefetch
    $SET_I_REL($Fail);
    Goto(*I);
}

JUMP(Fail) {
    //| -no_next
    $SET_I_REL($Fail);
    Goto(*I);
}

GC_SWAPOUT() {
    //
    // Since a garbage collection is expensive anyway, we can afford
    // to save the instruction counter so that the correct function will
    // be pointed in the crash dump if the garbage collection fails
    // because of insufficient memory.
    //
    SWAPOUT;
    c_p->i = I;
}

MAYBE_EXIT_AFTER_GC() {
    if (ERTS_PSFLG_EXITING & erts_atomic32_read_nob(&c_p->state)) {
        goto context_switch3;
    }
}

GC_TEST(Ns, Nh, Live) {
    Uint need = $Nh + $Ns;
    if (ERTS_UNLIKELY((E - HTOP) < (need + S_RESERVED))) {
       $GC_SWAPOUT();
       PROCESS_MAIN_CHK_LOCKS(c_p);
       FCALLS -= erts_garbage_collect_nobump(c_p, need, reg, $Live, FCALLS);
       ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
       PROCESS_MAIN_CHK_LOCKS(c_p);
       SWAPIN;
       $MAYBE_EXIT_AFTER_GC();
    }
    HEAP_SPACE_VERIFIED($Nh);
}

GC_TEST_PRESERVE(NeedHeap, Live, PreserveTerm) {
    Uint need = $NeedHeap;
    if (ERTS_UNLIKELY((E - HTOP) < (need + S_RESERVED))) {
       $GC_SWAPOUT();
       reg[$Live] = $PreserveTerm;
       PROCESS_MAIN_CHK_LOCKS(c_p);
       FCALLS -= erts_garbage_collect_nobump(c_p, need, reg, $Live+1, FCALLS);
       ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
       PROCESS_MAIN_CHK_LOCKS(c_p);
       $PreserveTerm = reg[$Live];
       SWAPIN;
       $MAYBE_EXIT_AFTER_GC();
    }
    HEAP_SPACE_VERIFIED($NeedHeap);
}


// Make sure that there are NeedStack + NeedHeap + 1 words available
// on the combined heap/stack segment, then decrement the stack
// pointer by (NeedStack + 1) words. Finally clear the word reserved
// for the continuation pointer at the top of the stack.
//
// Stack frame layout:
//
//       +-----------+
// y(N)  | Term      |
//       +-----------+
//            .
//            .
//            .
//       +-----------+
// y(0)  | Term      |
//       +-----------+
// E ==> | NIL or CP |
//       +-----------+
//
// When the function owning the stack frame is the currently executing
// function, the word at the top of the stack is NIL. When calling
// another function, the continuation pointer will be stored in the
// word at the top of the stack. When returning to the function
// owning the stack frame, the word at the stack top will again be set
// to NIL.

AH(NeedStack, NeedHeap, Live) {
    unsigned needed = $NeedStack + 1;
    $GC_TEST(needed, $NeedHeap, $Live);
    E -= needed;
    *E = NIL;
}


//
// Helpers for call instructions
//

DISPATCH() {
    BeamInstr dis_next;

    dis_next = *I;
    CHECK_ARGS(I);

    if (FCALLS > 0 || FCALLS > neg_o_reds) {
        FCALLS--;
        Goto(dis_next);
    } else {
        goto context_switch;
    }
}

DISPATCH_ABS(CallDest) {
    SET_I((BeamInstr *) $CallDest);
    DTRACE_LOCAL_CALL(c_p, erts_code_to_codemfa(I));

    $DISPATCH();
}

DISPATCH_EXPORT(Export) {
    BeamInstr dis_next;
    Export *ep;

    ep = (Export*)($Export);

    DTRACE_GLOBAL_CALL_FROM_EXPORT(c_p, ep);

    SET_I(ep->dispatch.addresses[erts_active_code_ix()]);
    CHECK_ARGS(I);
    dis_next = *I;

    if (ERTS_UNLIKELY(FCALLS <= 0)) {
        if (ERTS_PROC_GET_SAVED_CALLS_BUF(c_p) && FCALLS > neg_o_reds) {
            save_calls(c_p, ep);
        } else {
            goto context_switch;
        }
    }

    FCALLS--;
    Goto(dis_next);
}

DISPATCH_FUN(I) {
    BeamInstr dis_next;

    SET_I($I);

    dis_next = *I;
    CHECK_ARGS(I);

    if (FCALLS > 0 || FCALLS > neg_o_reds) {
        FCALLS--;
        Goto(dis_next);
    } else {
        goto context_switch;
    }
}

DISPATCH_REL(CallDest) {
    $SET_I_REL($CallDest);
    DTRACE_LOCAL_CALL(c_p, erts_code_to_codemfa(I));

    $DISPATCH();
}

DISPATCH_RETURN() {
    if (FCALLS > 0 || FCALLS > neg_o_reds) {
        FCALLS--;
        Goto(*I);
    } else {
        c_p->current = NULL;
        c_p->arity = 1;
        goto context_switch3;
    }
}

// Save the continuation pointer in the reserved slot at the
// top of the stack as preparation for doing a function call.

SAVE_CONTINUATION_POINTER(IP) {
    ASSERT(VALID_INSTR(*($IP)));
    *E = (BeamInstr) ($IP);
}

// Return to the function whose continuation pointer is stored
// at the top of the stack and set that word to NIL.

RETURN() {
    SET_I(cp_val(*E));
    *E = NIL;
}

NEXT0() {
    //| -no_next
    SET_I((BeamInstr *) $NEXT_INSTRUCTION);
    Goto(*I);
}

NEXT(Addr) {
    //| -no_next
    SET_I((BeamInstr *) $Addr);
    Goto(*I);
}

FAIL_BODY() {
    //| -no_prefetch
    goto find_func_info;
}

FAIL_HEAD_OR_BODY(Fail) {
    //| -no_prefetch

    /*
     * In a correctly working program, we expect failures in
     * guards to be more likely than failures in bodies.
     */

    if (ERTS_LIKELY($Fail)) {
        $FAIL($Fail);
    }
    goto find_func_info;
}

BADARG(Fail) {
    c_p->freason = BADARG;
    $FAIL_HEAD_OR_BODY($Fail);
}

BADARITH0() {
    c_p->freason = BADARITH;
    goto find_func_info;
}

SYSTEM_LIMIT(Fail) {
    c_p->freason = SYSTEM_LIMIT;
    $FAIL_HEAD_OR_BODY($Fail);
}

BIF_ERROR_ARITY_1(Fail, BIF, Op1) {
    //| -no_prefetch
    if (ERTS_LIKELY($Fail)) {
        $FAIL($Fail);
    }
    reg[0] = $Op1;
    SWAPOUT;
    I = handle_error(c_p, I, reg, &BIF_TRAP_EXPORT($BIF)->info.mfa);
    goto post_error_handling;
}

BIF_ERROR_ARITY_2(Fail, BIF, Op1, Op2) {
    //| -no_prefetch
    if (ERTS_LIKELY($Fail)) {
        $FAIL($Fail);
    }
    reg[0] = $Op1;
    reg[1] = $Op2;
    SWAPOUT;
    I = handle_error(c_p, I, reg, &BIF_TRAP_EXPORT($BIF)->info.mfa);
    goto post_error_handling;
}
